package com.tried.kind.algorithm.sort;

import java.util.Arrays;

/**
 * 堆排序
 */
public class HeapSort {


    /**
     * 堆的定义如下：n个元素的序列{k1,k2,···,kn}，当且仅当满足下关系时，称之为堆。
     *
     * ki <= k(2i)  且   ki <= k(2i+1)
     *
     * 或：   ki >= k(2i) 且 ki >= k(2i+1)
     *
     * 把此序列对应的二维数组看成一个完全二叉树。那么堆的含义就是：
     *      **完全二叉树中任何一个非叶子节点的值均不大于（或不小于）其左，右孩子节点的值。
     *      **由上述性质可知大顶堆的堆顶的关键字肯定是所有关键字中最大的，小顶堆的堆顶的关键字是所有关键字中最小的。
     *      因此我们可使用大顶堆进行升序排序, 使用小顶堆进行降序排序。
     *
     * 1、基本思想
     * 此处以大顶堆为例，堆排序的过程就是将待排序的序列构造成一个堆，选出堆中最大的移走，再把剩余的元素调整成堆，找出最大的再移走，重复直至有序。
     * 2、算法描述
     * ①. 先将初始序列K[1..n]建成一个大顶堆, 那么此时第一个元素K1最大, 此堆为初始的无序区.
     * ②. 再将关键字最大的记录K1 (即堆顶, 第一个元素)和无序区的最后一个记录 Kn 交换, 由此得到新的无序区K[1..n-1]和有序区K[n], 且满足K[1..n-1].keys <= K[n].key
     * ③. 交换K1 和 Kn 后, 堆顶可能违反堆性质, 因此需将K[1..n-1]调整为堆. 然后重复步骤②, 直到无序区只有一个元素时停止.
     *
     * 3、代码实现
     * 从算法描述来看，堆排序需要两个过程，一是建立堆，二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆函数，二是反复调用建堆函数以选择出剩余未排元素中最大的数来实现排序的函数。
     *
     * 总结起来就是定义了以下几种操作：
     *
     * 最大堆调整（Max_Heapify）：将堆的末端子节点作调整，使得子节点永远小于父节点
     * 创建最大堆（Build_Max_Heap）：将堆所有数据重新排序
     * 堆排序（HeapSort）：移除位在第一个数据的根节点，并做最大堆调整的递归运算
     * 对于堆节点的访问：
     *
     * 父节点i的左子节点在位置：(2*i+1);
     * 父节点i的右子节点在位置：(2*i+2);
     * 子节点i的父节点在位置：floor((i-1)/2);
     *
     *
     * 以上, ①. 建立堆的过程, 从length/2 一直处理到0, 时间复杂度为O(n); ②. 调整堆的过程是沿着堆的父子节点进行调整, 执行次数为堆的深度, 时间复杂度为O(lgn); ③. 堆排序的过程由n次第②步完成, 时间复杂度为O(nlgn).
     *
     * 平均时间复杂度	最好情况	最坏情况	空间复杂度
     * O(nlog2n)	O(nlog2n)	O(nlog2n)	O(1)
     * Tips: 由于堆排序中初始化堆的过程比较次数较多, 因此它不太适用于小序列. 同时由于多次任意下标相互交换位置, 相同元素之间原本相对的顺序被破坏了, 因此, 它是不稳定的排序.
     * @param args
     */
    public static void main(String[] args) {
        int[] arry = {5,9,3,4,79,46,89,26,75,8,7,1,55,234,567,234,67,78,34,32};
        System.out.println("初始："+Arrays.toString(arry));
        for (int i = arry.length;i>0;i--){
            max_heapify(arry,i);
            int temp = arry[0];
            arry[0] = arry[i-1];
            arry[i-1] = temp;
        }
    }

    public static void  max_heapify(int[] arry,int limit){
        if (arry.length < limit || arry.length < 0) return;
        int parentI = limit / 2;
        for (;parentI >=0; parentI--){
            if (parentI*2 >= limit){ continue;}
            int left = parentI * 2;
            int right = (left+1) >= limit ? left : (left+1) ;
            int maxI = arry[left] > arry[right] ? left : right;
            if (arry[parentI] < arry[maxI]){
                int temp = arry[parentI];
                arry[parentI] = arry[maxI];
                arry[maxI] = temp;
            }
            System.out.println("排序："+Arrays.toString(arry));
        }
    }
}
